/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details:
 * http://www.gnu.org/copyleft/lesser.html#TOC3
 */
package com.simplemonkey;


import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;


import com.jme.input.MouseInput;
import com.jme.math.Vector3f;
import com.jme.scene.SceneElement;
import com.simplemonkey.appearance.SpacingAppearance;
import com.simplemonkey.layout.ILayoutData;
import com.simplemonkey.tooltip.IToolTip;
import com.simplemonkey.util.Dimension;


/**
 * Implementation of a widget. A widget is the most basic unit of
 * a GUI system. 
 * <br/>
 * The background spans over the content, padding and border, but
 * not over the margin.<br/>
 * <br/>
 * The minium size of a widget is maintained by the widget itself and is
 * always kept up
 * to date, while the actual size of the widget is usually set by the layout manager.
 * Alternatively, one can set the actual size of a widget manually via the <code>setSize<method>.
 * 
 * 
 * @author Johannes Schaback, last edited by $Author: marcmenghin $, $Date: 2008-06-24 03:08:17 -0700 (Tue, 24 Jun 2008) $
 * @version $Revision: 569 $
 * @dedication Billie Holiday - They Can't Take That Away from Me
 * @see org.fenggui.background.Background
 * @see org.fenggui.border.Border
 */
public abstract class Widget implements IWidget
{

	private List<IResizeListener> resizeListeners = new CopyOnWriteArrayList<IResizeListener>();

	private List<IMouseListener> mouseListeners = new CopyOnWriteArrayList<IMouseListener>();
	
	private String name;
	


	private Dimension size = new Dimension(10, 10);
	Dimension minSize = new Dimension(10, 10);

	boolean shrinkable = true;
	boolean expandable = true;
	private boolean visible = true;

	private SpacingAppearance spacing = new SpacingAppearance(this);
	
	private IToolTip toolTipWidget;
	private String toolTipMessage = "";
	private boolean showToolTip = true;
	
	/**
	 * Whether or not mouse actions can fall through this widget to hit other widgets.
	 */
	private boolean permeable = false;
	
	/**
	 * The parent Container in which the Widget lays.
	 */
	private IContainer parent = null;


	/**
	 * Layout Data that is associated with this Widget. Layout data is
	 * required by the layout managers to know how to layout a Widget.
	 */
	private ILayoutData layoutData = null;

	/**
	 * Creates a new widget.
	 * @param name A name to be associated with this widget.
	 */
	public Widget(String name)
	{
		this.name = name;
		this.toolTipMessage = name;
	}
	
	/**
	 *  Creates a new widget.
	 */
	public Widget()
	{
		this("Widget");
	}

	
	public void addResizeListener(IResizeListener listener) {
		if(!resizeListeners.contains(listener))
			resizeListeners.add(listener);
	}

	
	public void removeResizeListener(IResizeListener listener) {
		resizeListeners.remove(listener);
	}

	
	
	
	public void addMouseListener(IMouseListener listener) {
		if(!mouseListeners.contains(listener))
			mouseListeners.add(listener);
	}

	
	public void removeMouseListener(IMouseListener listener) {
		mouseListeners.remove(listener);
		
	}

	
	public SpacingAppearance getSpacingAppearance() {
		return spacing;
	}

	/**
	 * Sets the LayoutData of this Widget. Note that the parameter is not
	 * type safe. If you pass a wrong type, the LayoutManager may crash
	 * with a ClassCashException.
	 * 
	 * @param layoutData layout data
	 */
	public void setLayoutData(ILayoutData layoutData)
	{
		this.layoutData = layoutData;
		if (getParent()!= null)
			getParent().updateMinSize();
	}

	/* (non-Javadoc)
	 * @see org.fenggui.IWidget#getLayoutData()
	 */
	public ILayoutData getLayoutData()
	{
		return layoutData;
	}

	/* (non-Javadoc)
	 * @see org.fenggui.IWidget#getParent()
	 */
	public IContainer getParent()
	{
		return parent;
	}

	public Dimension getMinContentSize()
	{
		return new Dimension(0, 0);
	}


	/* (non-Javadoc)
	 * @see org.fenggui.IWidget#layout()
	 */
	public void layout()
	{
		getSpatial().updateGeometricState(0, true);
	}

	/* (non-Javadoc)
	 * @see org.fenggui.IWidget#getDisplayX()
	 */
	public float getWorldX()
	{

		IContainer parent = getParent();
		if (parent != null)
		{
			return parent.getWorldX()  + getX();
		}
		return getX();
	}

	/* (non-Javadoc)
	 * @see org.fenggui.IWidget#getDisplayY()
	 */
	public float getWorldY()
	{

		IContainer parent = getParent();
		if (parent != null)
		{
			return parent.getWorldY() + getY();
		}
		return getY();
	}

	
	public IWidget getWidget(float x, float y)
	{
		/*
		if(insideMargin(x, y)) return this;
		return null;
		*/

		if (isVisible() && x >= 0 && y >= 0 && x <= getWidth()-  this.getSpacingAppearance().getMargin().getLeftPlusRight() && y <= getHeight()- this.getSpacingAppearance().getMargin().getBottomPlusTop())
			return this;

		return null;
	}

	public void setSizeToMinSize()
	{
		setSize(getMinSize());
	}

	public void setSize(Dimension s)
	{
		if(s == null)
			return;
		

		
			size = s;
			resize();
		
	}

	public void updateMinSize()
	{
		Dimension minSize;
		

		if (getSpacingAppearance() != null)
			minSize = getSpacingAppearance().getMinSizeHint();
		else
			minSize = new Dimension(1,1);

		if (minSize.equals(getMinSize()))
			return;
		
		setMinSize(minSize);
		
		if (getSize().getWidth() < getMinSize().getWidth())
		{
			if (getSize().getHeight() < getMinSize().getHeight())
			{
				this.setSize(getMinSize().getWidth(), getMinSize().getHeight());
			} else {
				this.setSize(getMinSize().getWidth(), getSize().getHeight());
			}
		} else {
			if (getSize().getHeight() < getMinSize().getHeight())
			{
				this.setSize(getSize().getWidth(), getMinSize().getHeight());
			}
		}

		
	 if (getParent() != null)
			getParent().updateMinSize();
		
	}

	public void setPosition(float x, float y)
	{
		this.setPosition(new Vector3f(x, y,0));
	}

	public final void setParent(IContainer parent)
	{
		this.parent = parent;
	}

	/*
	public String toString()
	{
		StringBuffer sb = new StringBuffer();
		sb.append("--- ");
		sb.append(this.getClass().getSimpleName());
		sb.append(" ---\n");
		sb.append("size    : ");
		sb.append(size);
		sb.append('\n');
		sb.append("position: ");
		sb.append(getPosition());
		sb.append('\n');
		sb.append("minSize : ");
		sb.append(minSize);

		return sb.toString();
	}*/

	/**
	 * Sets the local <code>y</code> coordinate of this widget. Note that widget coordinates are measured
	 * in the coordinate system of its parent container.
	 * @param y <code>y</code> coordinate
	 */
	public void setY(float y)
	{
		this.setPosition(new Vector3f(getX(), y,0));
	}


	public float getY()
	{
		return getPosition().getY();
	}



	public boolean isTraversable()
	{
		return false;
	}

	public float getWidth()
	{
		return size.getWidth();
	}

	public float getHeight()
	{
		return size.getHeight();
	}

	public boolean isExpandable()
	{
		return expandable;
	}

	public void setExpandable(boolean expandable)
	{
		this.expandable = expandable;
	}

	public boolean isShrinkable()
	{
		return shrinkable;
	}

	public Dimension getSize()
	{
		return size;
	}

	public Dimension getMinSize()
	{
		return minSize;
	}

	public void setShrinkable(boolean shrinkable)
	{
		this.shrinkable = shrinkable;
	}

	public void setMinSize(Dimension dim)
	{
			minSize.setSize(dim);
	}

	protected void resize() {
		for(IResizeListener listener:resizeListeners)
			listener.resize(this.getSize());
	}

	public Vector3f getPosition()
	{
		return getSpatial().getLocalTranslation();
	}

	public float getMinWidth()
	{
		return getMinSize().getWidth();
	}

	public float getMinHeight()
	{
		return getMinSize().getHeight();
	}

	public void setMinSize(float minWidth, float minHeight)
	{
		setMinSize(new Dimension(minWidth, minHeight));
	}

	public void setSize(float width, float height)
	{
		setSize(new Dimension(width, height));
	}

	public void setHeight(float height)
	{
		setSize(new Dimension(getWidth(), height));
	}

	public void setWidth(float width)
	{
		setSize(new Dimension(width, getHeight()));
	}

	/**
	 * Sets the local <code>x coordinate of this widget. Note that widget coordinates are measured
	 * in the coordinate system of its parent container.
	 * @param x <code>x coordinate
	 */
	public void setX(float x)
	{
		this.setPosition(new Vector3f(x, getY(),0));
	}

	/**
	 * Abbreviation for <code>getPosition().getX()</code>.
	 */
	public float getX()
	{
		return  getPosition().getX();
	}

	public void setPosition(Vector3f p)
	{

		this.getSpatial().getLocalTranslation().set(p);
		this.getSpatial().updateGeometricState(0, true);
	}
	
	

	public boolean isVisible()
	{
		if (getParent() != null)
			return visible && getParent().isVisible();
		else
			return visible;
	}

	public void setVisible(boolean visible)
	{
		if(this.visible==visible)
			return;
		this.visible = visible;
		if (getParent() != null)
		{
//			getParent().updateMinSize();
			getParent().layout();
		}
		if(visible)
		{
			getSpatial().setCullMode(SceneElement.CULL_INHERIT);
		}else
		{
			getSpatial().setCullMode(SceneElement.CULL_ALWAYS);
		}
	}


	
	public boolean testCollision(Vector3f position) {
		return position.x >this.getWorldX() && position.x - this.getWorldX() < this.getWidth() - this.getSpacingAppearance().getMargin().getLeftPlusRight() &&
			position.y > this.getWorldY() && position.y - this.getWorldY() < this.getHeight() - this.getSpacingAppearance().getMargin().getBottomPlusTop();
	}

	
	public void mouseOff() {
		if(this.showToolTip() && this.getToolTipWidget() != null)
		{
			this.getToolTipWidget().hideToolTip(this);
		}
		
		for(IMouseListener listener:this.mouseListeners)
			listener.mouseOff();
		
	}

	
	public boolean mouseMove(float x, float y) {
		if(this.showToolTip() && this.getToolTipWidget() != null)
		{
			this.getToolTipWidget().showToolTip(this, this.getToolTip(),x + getWorldX(),y + getWorldY());
		}
		
		for(IMouseListener listener:this.mouseListeners)
			listener.mouseMove(x,y);
		return !this.isPermeable();
	}

	/**
	 * Whether or not this widget allows mouse actions to pass through to widgets below it.
	 * @return
	 */
	public boolean isPermeable() {
		return permeable;
	}

	
	public void setPermeable(boolean permeable) {
		this.permeable = permeable;
	}

	public boolean mousePress(boolean pressed, int button,float x, float y){
		for(IMouseListener listener:this.mouseListeners)
			listener.mousePress(pressed, button, x, y);
		return !isPermeable();
	}

	
	public String toString() {
		return getName();
	}

	public String getName() {
		return name;
	}
	
	/**
	 * Tests if the current mouse position touches this widget
	 * @return
	 */
	public boolean isMouseTouching()
	{
		Vector3f mousePosition = new Vector3f(MouseInput.get().getXAbsolute(),MouseInput.get().getYAbsolute(),0);
		
		return this.testCollision(mousePosition); 

	}
	public void setWorldPosition(float x, float y) {
		if (parent == null)
			this.setPosition(x, y);
		else
		{
			this.setPosition(x - parent.getWorldX(), y- parent.getWorldY());
			
			
		}
		
	}

	public String getToolTip() {
		return this.toolTipMessage;
	}

	public boolean showToolTip() {
		return this.showToolTip;
	}

	public IToolTip getToolTipWidget() {
		return toolTipWidget;
	}

	public void setToolTip(String toolTip) {
		this.toolTipMessage = toolTip;
		
	}

	public void setTooltipWidget(IToolTip toolTipWidget) {
		this.toolTipWidget = toolTipWidget;
		
	}

	public void setShowToolTip(boolean showToolTip) {
		this.showToolTip = showToolTip;
		
	}
	
}

